﻿// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Text;
using System.Threading.Actors.Utilities;
using System.Threading.Tasks;

namespace System.Threading.Actors
{
    // TODO: Do we really need IActorPromise?  Could we live with just this class?
    // Provides a reference implementation of IActorPromise
    public class ActorPromise : IActorPromise
    {
        private IActorState m_state;
        private Task<string> m_promise;
        private Task<IActorProxy> m_proxyCreationTask;

        public ActorPromise(IActorState state, Task<string> promise)
        {
            Contract.Requires(state != null, "Expected non-null state");
            Contract.Requires(promise != null, "Expected non-null promise");

            m_state = state;
            m_promise = promise;
            m_proxyCreationTask = null;
        }

        // TODO: Should be internal, but its usage spans assemblies
        public ActorPromise(IActorState state, Task<IActorProxy> proxyCreationTask)
        {
            Contract.Requires(state != null, "Expected non-null state");
            Contract.Requires(proxyCreationTask != null, "Expected non-null proxyCreationTask");

            m_state = state;
            m_proxyCreationTask = proxyCreationTask;
            m_promise = null;
        }

        Task ReferenceTask { get { return m_promise == null ? (Task) m_proxyCreationTask : (Task) m_promise; } }

        public string Result
        {
            get
            {
                if (m_promise != null) return m_promise.Result;
                else throw new NotSupportedException("Not supported with IActorProxy version");
            }
        }

        public T GetTypedResult<T>() 
        {
            if (m_promise != null)
                return SerializationHelper.DeserializeToType<T>(Result);
            else
            {
                if (typeof(T) == typeof(IActorProxy)) return (T)(object)m_proxyCreationTask.Result;
                else throw new InvalidOperationException("Could not do GetTypedResult<T> of proxyCreationTask promise where T != IActorProxy");
            }
        }

        // Task.ContinueWith doesn't make the first task block until the continuation completes.  It returns
        // a second Task on which you can block, if necessary.
        public IActorPromise ContinueWith(string methodName, params Object[] parameters)
        {
            Task predecessor = ReferenceTask;
            //Console.WriteLine("ActorPromise.ContinueWith({0})", methodName);
            var continuation = predecessor.ContinueWith((antecedent, stateAsObject) =>
            {
                //Console.WriteLine("ActorPromise.ContinueWith delegate: antecedent is {0}", antecedent.Status);
                //if (!antecedent.IsFaulted) Console.WriteLine("ActorPromise.ContinueWith delegate: result = {0}", antecedent.Result);

                var state = (IActorState)stateAsObject;
                Object[] combinedParams = new Object[(parameters == null) ? 1 : parameters.Length + 1];
                combinedParams[0] = this;
                if (parameters != null)
                    Array.Copy(parameters, 0, combinedParams, 1, parameters.Length);
                var task = MethodCallUtilities.CallMethodOnStateAsyncThrowing(state, methodName, false, combinedParams);
                // Synchronously blocking here so that the outer Task doesn't complete until we have
                // finished this method call.   Should improve consistency.
                task.Wait();
                // Make the continuation a Task<String>, so it fits nicely with ActorPromise's existing constructors.
                return SerializationHelper.Serialize(task.Result);
            }, m_state, TaskContinuationOptions.PreferFairness);
            // Don't want ExecuteSynchronously, because this could result in method calls being nested at the very
            // top level.  This would in turn result in the nested method call waiting on the Flush()-produced
            // waiter task while holding the state lock (in the outer call), which would result in deadlock.

            return new ActorPromise(m_state, continuation);
        }

        // Task.IsFaulted doesn't block until the task completes.  So, users should accept polling, or block themselves by
        // calling Wait or asking for the result.  Both of those will throw if the task faulted.  There's no good way to ask 
        // "please block until the task completes but do not throw an exception".  
        public bool IsFaulted
        {
            get
            {
                Task task = ReferenceTask;
                // Task.IsFaulted doesn't wait for the task to be completed - it tells you whether there has been a fault so far.
                if (!task.IsCompleted)
                {
                    try
                    {
                        task.Wait();
                    }
                    catch (AggregateException) { }
                }
                return task.IsFaulted;
            }
        }

        public void Wait()
        {
            ReferenceTask.Wait();
        }

        public bool Wait(int timeoutMilliseconds)
        {
            return ReferenceTask.Wait(timeoutMilliseconds);
        }

        public string ExceptionType
        {
            get 
            {
                Task task = ReferenceTask;
                return task.IsFaulted ? task.Exception.GetBaseException().GetType().Name : null; 
            }
        }
        public string ExceptionMessage
        {
            get 
            {
                Task task = ReferenceTask;
                return task.IsFaulted ? task.Exception.GetBaseException().Message : null; 
            }
        }
        public string ExceptionStackTrace
        {
            get 
            {
                Task task = ReferenceTask;
                return task.IsFaulted ? task.Exception.GetBaseException().StackTrace : null; 
            }
        }

        public override string ToString()
        {
            if (m_promise != null)
            {
                if (!m_promise.IsCompleted) return "<promise:pending>";
                else if (m_promise.IsFaulted) return "<promise:faulted>";
                else return "<promise:" + m_promise.Result + ">";
            }
            else
            {
                if (!m_proxyCreationTask.IsCompleted) return "<proxyPromise:pending>";
                else if (m_proxyCreationTask.IsFaulted) return "<proxyPromise:faulted>";
                else return "<proxyPromise:completed>";
            }
        }
    }
}
